package deployment

import (
	"context"
	"fmt"
	"github.com/sirupsen/logrus"
	"k3s-api-test/pkg/utils"
	v01 "k8s.io/api/apps/v1"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/client-go/kubernetes"
	"reflect"
	"time"
)

const (
	testNamespace = "default"       // 定义测试使用的命名空间
	testTimeout   = 1 * time.Minute // 单个测试用例的默认超时时间
	nodeName      = "agent1"        // 指定node节点名称
	appName       = "nginx"
)

func RunDeploymentTests(clientset *kubernetes.Clientset, log *logrus.Logger, selectedTestCase string) {
	log.Info("Starting Pods API tests...")

	// 运行具体的测试用例
	switch selectedTestCase {
	case "basic":
		runTestCase(clientset, log, "Basic_Deployment_Creation_Deletion", testDeploymentBasicCreationDeletion)
	case "deploy":
		runTestCase(clientset, log, "Deployment_Fields", testDeploymentFields)
	case "patch":
		runTestCase(clientset, log, "Deployment_Patch", testDeploymentPatch)
	default:
		log.Warnf("No test case selected or invalid test case name '%s'. No tests will be executed. Available tests: basic, containers, pod.", selectedTestCase)
		// 这里不调用 runTestCase，也不做任何其他操作，直接返回
	}
	log.Info("This Deployment API test completed.")
}

// runTestCase 是一个通用的测试用例执行器
func runTestCase(clientset *kubernetes.Clientset, log *logrus.Logger, name string, testFunc func(*kubernetes.Clientset, *logrus.Logger) error) {
	log.Infof("--- Running Test Case: %s ---", name)
	err := testFunc(clientset, log)
	if err != nil {
		log.Errorf("Test Case %s FAILED: %v", name, err)
	} else {
		log.Infof("Test Case %s PASSED", name)
	}
}

func testDeploymentBasicCreationDeletion(clientset *kubernetes.Clientset, log *logrus.Logger) error {
	// 这里，注意，必须是 DNS 子域名,只能包含小写字母，数组 '-'
	deploymentName := utils.GenerateUniqueName("basic-deployment")
	testLabels := map[string]string{"app": appName}
	// 需要为Selector新建一个labelSelector
	labelSelector := metav1.LabelSelector{
		MatchLabels: map[string]string{
			"app": appName,
		},
		// MatchExpressions
	}
	//selectorLables := map[string]string{"kubernetes.io/hostname": nodeName}
	// 这个需要注意，Deployment来自于app/v1,而Pod来自于core/v1
	// app需要用到core,所以core应该是v1,而app避免重复这里我们用v01
	template := v1.PodTemplateSpec{
		ObjectMeta: metav1.ObjectMeta{
			Labels: testLabels,
		},
		Spec: v1.PodSpec{
			Containers: []v1.Container{
				{
					Name:  "test-nginx",
					Image: "jimlt.bfsmlt.top/nginx:test",
					Ports: []v1.ContainerPort{
						{ContainerPort: 79},
					},
				},
			},
		},
	}

	deployment := &v01.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name:      deploymentName,
			Namespace: testNamespace,
			Labels:    testLabels,
		},
		Spec: v01.DeploymentSpec{
			// 这里注意，Replicas 是 *int32指针类型，默认为1，所以我们需要将 int32 转为 *int32
			Replicas: utils.PtrInt32(3),
			Paused:   false,
			// 这是 Pod 的标签选择器，需要与 Pod 的 template 标签对应
			// 已经将 MatchLabels字段包裹进去了？
			Selector: &labelSelector,
			Template: template,
			// 截止时间参数，单位默认为秒，在标志状态为失败之前需要等待的时长
			//ProgressDeadlineSeconds: utils.PtrInt32(100),
		},
	}

	defer func() {
		log.Debugf("Cleaning up delpoyment %s...", deploymentName)
		err := DeleteDeployment(clientset, testNamespace, deploymentName, log)
		if err != nil {
			log.Errorf("Failed to clean up deployment")
		} else {
			log.Debugf("Deployment %s cleaned up.", deploymentName)
		}
	}()

	// 操作 deployment
	// 1.创建 Dep
	createdDep, err := CreateDeployment(clientset, deployment, testNamespace, log)
	if err != nil {
		return fmt.Errorf("failed to create deployment: %w", err)
	}
	// 2.验证状态,这里的长度为什么是0？
	log.Info(len(createdDep.Status.Conditions))
	//err = utils.CheckDeploymentStatus(clientset, deploymentName, testNamespace, testTimeout, log)
	//if err != nil {
	//	return fmt.Errorf("deployment %s did not reach expected conditions", createdDep.Name)
	//}
	// 3.验证上面编写的字段
	fetchedDeployment, err := clientset.AppsV1().Deployments(testNamespace).Get(context.TODO(), createdDep.Name, metav1.GetOptions{})
	if err != nil {
		return fmt.Errorf("failed to get deployment %s after creation: %w", createdDep.Name, err)
	}
	// 验证标签字段
	if fetchedDeployment.Labels["app"] != "nginx" {
		return fmt.Errorf("pod labels mismatch: expected 'app:nginx', got %v", fetchedDeployment.Labels)
	}
	// 4.验证源码中给定字段，例如 DeploymentStatus下的字段
	// 这些验证可以之后具体字段中写，我们先写一个简单的启动和删除
	log.Debugf("Deloyment %s successfully created and is running.", deploymentName)
	return nil
}

// testDeploymentFields 测试deployment内部spec中的所有常用字段
func testDeploymentFields(clientset *kubernetes.Clientset, log *logrus.Logger) error {
	deploymentName := utils.GenerateUniqueName("basic-deployment")
	testLabels := map[string]string{"app": "web"}
	// 必须和 Pod template的标签相同
	labelSelector := metav1.LabelSelector{
		MatchLabels: map[string]string{
			"app": "web",
		},
		// MatchExpressions
	}

	// template 就是描述 Pod，可以包含之前Pod中所有测试过的字段
	template := v1.PodTemplateSpec{
		ObjectMeta: metav1.ObjectMeta{
			Labels: testLabels,
		},
		Spec: v1.PodSpec{
			Containers: []v1.Container{
				{
					Name:  "test-nginx",
					Image: "jimlt.bfsmlt.top/nginx:test",
					Ports: []v1.ContainerPort{
						{ContainerPort: 79},
					},
					Resources: v1.ResourceRequirements{
						Limits: v1.ResourceList{
							v1.ResourceCPU:    resource.MustParse("100m"),
							v1.ResourceMemory: resource.MustParse("256Mi"),
						},
						Requests: v1.ResourceList{
							v1.ResourceCPU:    resource.MustParse("50m"),
							v1.ResourceMemory: resource.MustParse("128Mi"),
						},
					},
				},
			},
		},
	}

	var replicasCount int32 = 3

	deployment := &v01.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name:      deploymentName,
			Namespace: testNamespace,
			Labels:    testLabels,
		},
		Spec: v01.DeploymentSpec{
			// 这里注意，Replicas 是 *int32指针类型，默认为1，所以我们需要将 int32 转为 *int32
			Replicas: &replicasCount,
			Paused:   false,
			// 这是 Pod 的标签选择器，需要与 Pod 的 template 标签对应
			Selector: &labelSelector,
			Template: template,
			// 截止时间参数，单位默认为秒，在标志状态为失败之前需要等待的时长
			ProgressDeadlineSeconds: utils.PtrInt32(30),
			Strategy: v01.DeploymentStrategy{
				// 默认是 RollingUpdate,意味着新版本将逐步部署到 Pod 中，以避免停机时间；另外一种Recreate会杀死所有旧的pod
				Type: v01.RollingUpdateDeploymentStrategyType,
				// 这里还有一个 rollingUpdate参数，config params，意味着需要一个配置文件
			},
			// 保留旧的副本数量，允许回滚。默认为10
			RevisionHistoryLimit: utils.PtrInt32(5),
		},
	}

	defer func() {
		log.Debugf("Cleaning up delpoyment %s...", deploymentName)
		err := DeleteDeployment(clientset, testNamespace, deploymentName, log)
		if err != nil {
			log.Errorf("Failed to clean up deployment")
		} else {
			log.Debugf("Deployment %s cleaned up.", deploymentName)
		}
	}()
	createdDep, err := CreateDeployment(clientset, deployment, testNamespace, log)
	if err != nil {
		return fmt.Errorf("failed to create deployment: %w", err)
	}
	log.Info(createdDep.Spec.Strategy)
	// 验证这些字段
	fetchedDeployment, err := clientset.AppsV1().Deployments(testNamespace).Get(context.TODO(), createdDep.Name, metav1.GetOptions{})
	log.Infof("deployment matched labels %v", fetchedDeployment.Labels)
	if fetchedDeployment.Namespace != testNamespace {
		return fmt.Errorf("deployment namespace mismatch : %w", err)
	}
	log.Infof("deployment namespace matched %s", fetchedDeployment.Namespace)
	//
	if fetchedDeployment.Spec.Replicas == nil {
		return fmt.Errorf("fetchedDeployment.Spec.Replicas is nil, cannot compare")
	}
	if *fetchedDeployment.Spec.Replicas != *createdDep.Spec.Replicas { // 注意这里需要解引用指针，别比成地址了
		return fmt.Errorf("deployment replicas mismatch : %d", *fetchedDeployment.Spec.Replicas)
	}
	log.Infof("deployment replicas matched %d", *fetchedDeployment.Spec.Replicas)
	//
	if fetchedDeployment.Spec.Paused != createdDep.Spec.Paused {
		return fmt.Errorf("deployment paused mismatch : %w", err)
	}
	log.Infof("deployment paused matched %t", fetchedDeployment.Spec.Paused)
	//
	if !labels.Equals(fetchedDeployment.Spec.Selector.MatchLabels, createdDep.Spec.Selector.MatchLabels) {
		return fmt.Errorf("deployment selector MatchLabels mismatch: fetched=%v, created=%v",
			fetchedDeployment.Spec.Selector.MatchLabels, createdDep.Spec.Selector.MatchLabels)
	}
	log.Infof("deployment selector matched %v", fetchedDeployment.Spec.Selector.MatchLabels)
	//
	if *fetchedDeployment.Spec.ProgressDeadlineSeconds != *createdDep.Spec.ProgressDeadlineSeconds {
		return fmt.Errorf("deployment ProgressDeadlineSeconds mismatch : %w", err)
	}
	log.Infof("deployment ProgressDeadlineSeconds matched %d", *fetchedDeployment.Spec.ProgressDeadlineSeconds)
	// DeepEqual彻底比较结构体
	if !reflect.DeepEqual(fetchedDeployment.Spec.Strategy, createdDep.Spec.Strategy) {
		// 如果不相等，返回详细的错误信息
		return fmt.Errorf("deployment Strategy mismatch: fetched=%+v, created=%+v",
			fetchedDeployment.Spec.Strategy, createdDep.Spec.Strategy)
	}
	// 使用 %v 或 %+v 来打印结构体的内容
	log.Infof("deployment Strategy matched %+v", fetchedDeployment.Spec.Strategy)
	//
	if *fetchedDeployment.Spec.RevisionHistoryLimit != *createdDep.Spec.RevisionHistoryLimit {
		return fmt.Errorf("deployment RevisionHistoryLimit mismatch : %w", err)
	}
	log.Infof("deployment RevisionHistoryLimit matched %d", *fetchedDeployment.Spec.RevisionHistoryLimit)
	log.Debugf("Deployment %s all fileds verified successfully.", fetchedDeployment.Name)
	return nil
}

// Patch 有几种策略，例如策略性合并StrategicMergePatch,JSONPatch，MergePatch是用一整个json替换，而默认的策略是StrategicMergePatch
func testDeploymentPatch(clientset *kubernetes.Clientset, log *logrus.Logger) error {
	patchDataImage := map[string]interface{}{
		"spec": map[string]interface{}{
			"template": map[string]interface{}{
				"spec": map[string]interface{}{
					"containers": []map[string]interface{}{
						{
							"name":  "busybox-test",
							"image": "jimlt.bfsmlt.top/busybox:v1.0",
						},
					},
				},
			},
		},
	}
	updatedDeploy, err := PatchDeployment(clientset, "deploy-patch-test", testNamespace, patchDataImage, log)

	if err != nil {
		log.Errorf("Error updating deployment image: %v", err)
	} else {
		log.Infof("Deployment image updated to %s", updatedDeploy.Spec.Template.Spec.Containers[0].Image)
	}
	return nil
}
